﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace AsmResolver.PE.Win32Resources.Dfm
{
    [Flags]
    public enum FilerFlag
    {
        ffInherited = 1,
        ffChildPos = 2,
        ffInline = 4
    }
    public enum ValueType : byte
    {
        vaNull,
        vaList,
        vaInt8,
        vaInt16,
        vaInt32,
        vaExtended,
        vaString,
        vaIdent,
        vaFalse,
        vaTrue,
        vaBinary,
        vaSet,
        vaLString,
        vaNil,
        vaCollection,
        vaSingle,
        vaCurrency,
        vaDate,
        vaWString,
        vaInt64,
        vaUTF8String,
        vaDouble
    }
    public class Reader : BinaryReader
    {
        public Reader(Stream input) : base(input)
        {
        }

        public Reader(Stream input, Encoding encoding) : base(input, encoding)
        {
        }

        public Reader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen)
        {
        }
        public void ReadPrefix(ref FilerFlag flags, ref int childPos)
        {
            flags = 0;
            if (((byte)NextValue() & 0xF0) == 0xF0)
            {
                var Prefix = (byte)ReadValue();
                flags = (FilerFlag)(Prefix & 0x0F);
                if ((flags & FilerFlag.ffChildPos) == FilerFlag.ffChildPos)
                    childPos = ReadInt32();
            }
        }

        public ValueType NextValue()
        {
            var result = ReadValue();
            BaseStream.Position--;
            return result;
        }
        public ValueType ReadValue()
        {
            var Buf = ReadByte();
            return (ValueType)Buf;
        }
        const uint FilerSignature = 0x30465054;

        public void ReadSignature()
        {
            var Signature = ReadUInt32();
            if (Signature != FilerSignature)
                throw new InvalidDataException();
        }

        internal bool EndOfList()
        {
            var result = ReadValue() == ValueType.vaNull;
            BaseStream.Position--;
            return result;
        }

        internal void ReadListEnd()
        {
            CheckValue(ValueType.vaNull);
        }

        public void CheckValue(ValueType value)
        {
            if (ReadValue() != value)
            {
                BaseStream.Position--;
                SkipValue();
                throw new Exception();
            }
        }

        private void SkipValue()
        {
            ;
        }

        internal string ReadStr()
        {
            var len = ReadByte();
            byte[] Buf = ReadBytes(len);
            return Encoding.UTF8.GetString(Buf);
        }

        public double ReadFloat()
        {
            if (ReadValue() == ValueType.vaExtended)
                ReadBytes(10);
            else
            {
                BaseStream.Position--;
                return ReadDouble();
            }
            return 0;
        }

        public double ReadDouble()
        {
            if (ReadValue() == ValueType.vaDouble)
                ReadBytes(8);
            else
            {
                BaseStream.Position--;
                return ReadInt64();
            }
            return 0;
        }

        public new Int64 ReadInt64()
        {
            if (ReadValue() == ValueType.vaInt64)
                return base.ReadInt64();
            else
            {
                BaseStream.Position--;
                return ReadInteger();
            }
        }
        public float ReadSingle()
        {
            if (ReadValue() == ValueType.vaSingle)
                ReadBytes(4);
            else
            {
                BaseStream.Position--;
                return ReadInt64();
            }
            return 0;
        }

        internal double ReadCurrency()
        {
            if (ReadValue() == ValueType.vaCurrency)
                ReadBytes(8);
            else
            {
                BaseStream.Position--;
                return ReadInt64();
            }
            return 0;
        }

        void PropValueError()
        {
            throw new Exception();
        }
        internal int ReadInteger()
        {
            int result = 0;
            switch (ReadValue())
            {
                case ValueType.vaInt8:
                    result = ReadSByte();
                    break;
                case ValueType.vaInt16:
                    result = ReadInt16();
                    break;
                case ValueType.vaInt32:
                    result = ReadInt32();
                    break;
                default:
                    PropValueError();
                    break;
            }
            return result;
        }

        internal DateTime ReadDate()
        {
            if (ReadValue() == ValueType.vaDate)
            {
                return DateTime.FromOADate(base.ReadDouble());
            }
            else
            {
                BaseStream.Position--;
                return new DateTime(ReadInt64());
            }
        }

        public string ReadString(int codePage = 0)
        {
            var L = 0;
            switch (ReadValue())
            {
                case ValueType.vaWString:
                    L = base.ReadInt32();
                    return Encoding.Unicode.GetString(ReadBytes(L));
                case ValueType.vaUTF8String:
                    L = base.ReadInt32();
                    return Encoding.UTF8.GetString(ReadBytes(L));
                case ValueType.vaString:
                    L = base.ReadByte();
                    return Encoding.GetEncoding(codePage).GetString(ReadBytes(L));
                case ValueType.vaLString:
                    L = base.ReadInt32();
                    return Encoding.GetEncoding(codePage).GetString(ReadBytes(L));
            }
            PropValueError();
            return "";
        }

        internal string ReadIdent()
        {
            switch (ReadValue())
            {
                case ValueType.vaIdent:
                    byte l = ReadByte();
                    return Encoding.UTF8.GetString(ReadBytes(l));
                case ValueType.vaFalse:
                    return "False";
                case ValueType.vaTrue:
                    return "True";
                case ValueType.vaNil:
                    return "nil";
                case ValueType.vaNull:
                    return "Null";
            }
            PropValueError();
            return "";
        }
    }
}
